package org.jivesoftware.smackx.packet;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.util.Iterator;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.EncryptionUtils;
import org.jivesoftware.smackx.provider.EncryptedDataProvider;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
public class SignedPresence implements PacketExtension {
private String signedStanza;
@Override
public String getNamespace() {
return "jabber:x:signed";
}
@Override
public String getElementName() {
return "x";
}
@Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">");
buf.append(signedStanza);
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
public void signAndSet(String stanza, String keyPath, String keyPwd) throws XMPPException {
try {
signedStanza = sign(stanza, keyPath, keyPwd.toCharArray());
} catch (Exception e) {
throw new XMPPException(e);
}
}
@SuppressWarnings("rawtypes")
private static String sign(String stanza, String keyPath, char[] pass) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException {
if (stanza == null) {
stanza = "";
}
PGPSecretKey pgpSecKey = EncryptionUtils.readSecretKey(keyPath);
PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass));
PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC"));
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
Iterator it = pgpSecKey.getPublicKey().getUserIDs();
if (it.hasNext()) {
spGen.setSignerUserID(false, (String) it.next());
sGen.setHashedSubpackets(spGen.generate());
}
InputStream fIn = new BufferedInputStream(new ByteArrayInputStream(stanza.getBytes()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ArmoredOutputStream aOut = new ArmoredOutputStream(out);
aOut.beginClearText(PGPUtil.SHA1);
//
// note the last \n/\r/\r\n in the file is ignored
//
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
int lookAhead = readInputLine(lineOut, fIn);
processLine(aOut, sGen, lineOut.toByteArray());
if (lookAhead != -1) {
do {
lookAhead = readInputLine(lineOut, lookAhead, fIn);
sGen.update((byte) '\r');
sGen.update((byte) '\n');
processLine(aOut, sGen, lineOut.toByteArray());
} while (lookAhead != -1);
}
fIn.close();
aOut.endClearText();
BCPGOutputStream bOut = new BCPGOutputStream(aOut);
sGen.generate().encode(bOut);
aOut.close();
String signed = new String(out.toByteArray());
bOut.close();
return EncryptedDataProvider.removeHeaderFooter(signed);
}
public String verifyAndGet(String buddyKey) throws XMPPException {
try {
return verify(signedStanza, buddyKey);
} catch (Exception e) {
throw new XMPPException(e);
}
}
private static String verify(String signedStanza, String keyIn) throws Exception {
InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(signedStanza.getBytes()));
PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(new FileInputStream(keyIn)));
PGPPublicKey key;
PGPObjectFactory pgpFact = new PGPObjectFactory(in);
Object obj = pgpFact.nextObject();
if (obj instanceof PGPCompressedData) {
PGPCompressedData c1 = (PGPCompressedData) obj;
pgpFact = new PGPObjectFactory(c1.getDataStream());
obj = pgpFact.nextObject();
}
if (obj instanceof PGPOnePassSignatureList) {
PGPOnePassSignatureList p1 = (PGPOnePassSignatureList) obj;
PGPOnePassSignature ops = p1.get(0);
key = pgpRing.getPublicKey(ops.getKeyID());
PGPLiteralData p2 = (PGPLiteralData) pgpFact.nextObject();
InputStream dIn = p2.getInputStream();
int ch;
ByteArrayOutputStream out = new ByteArrayOutputStream();
ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key);
while ((ch = dIn.read()) >= 0) {
ops.update((byte) ch);
out.write(ch);
}
String sout = new String(out.toByteArray());
out.close();
PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject();
if (ops.verify(p3.get(0))) {
return sout;
} else {
return null;
}
}
if (obj instanceof PGPSignatureList) {
PGPSignatureList p1 = (PGPSignatureList) obj;
PGPSignature s1 = p1.get(0);
key = pgpRing.getPublicKey(s1.getKeyID());
s1.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key);
if (s1.verify()) {
return "ok";
} else {
return null;
}
}
return null;
}
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) throws IOException {
bOut.reset();
int lookAhead = -1;
int ch;
while ((ch = fIn.read()) >= 0) {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
}
return lookAhead;
}
private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) throws IOException {
bOut.reset();
int ch = lookAhead;
do {
bOut.write(ch);
if (ch == '\r' || ch == '\n') {
lookAhead = readPassedEOL(bOut, ch, fIn);
break;
}
} while ((ch = fIn.read()) >= 0);
if (ch < 0) {
lookAhead = -1;
}
return lookAhead;
}
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException {
int lookAhead = fIn.read();
if (lastCh == '\r' && lookAhead == '\n') {
bOut.write(lookAhead);
lookAhead = fIn.read();
}
return lookAhead;
}
private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line) throws SignatureException, IOException {
// note: trailing white space needs to be removed from the end of
// each line for signature calculation RFC 4880 Section 7.1
int length = getLengthWithoutWhiteSpace(line);
if (length > 0) {
sGen.update(line, 0, length);
}
aOut.write(line, 0, line.length);
}
private static boolean isLineEnding(byte b) {
return b == '\r' || b == '\n';
}
private static int getLengthWithoutWhiteSpace(byte[] line) {
int end = line.length - 1;
while (end >= 0 && isWhiteSpace(line[end])) {
end--;
}
return end + 1;
}
private static boolean isWhiteSpace(byte b) {
return isLineEnding(b) || b == '\t' || b == ' ';
}
public String getSignedStanza() {
return signedStanza;
}
public void setSignedStanza(String signedStanza) {
this.signedStanza = signedStanza;
}
}